﻿using System.IO;
using System.Xml;
using System.Diagnostics;
using System;

namespace AllTalkProxy
{
    public class ResponseParser
    {
        Listener _listener;
        private static NameTable _nameTable;
        private static XmlReaderSettings _settings;
        private static XmlNamespaceManager _namespaceManager;
        private static XmlParserContext _parserContext;
        private static object _presence;
        private static object _iq;
        private static object _bind;
        private static object _jid;
        private static object _message;
        private static object _query;
        private static object _vcard;
        private static object _failure;
        private static object _notAuthorized;

        public ResponseParser(Listener listener)
        {
            _listener = listener;
            if (_nameTable == null)
            {
                _nameTable = new NameTable();
                _presence = _nameTable.Add("presence");
                _iq = _nameTable.Add("iq");
                _bind = _nameTable.Add("bind");
                _jid = _nameTable.Add("Jid");
                _message = _nameTable.Add("message");
                _query = _nameTable.Add("query");
                _vcard = _nameTable.Add("vCard");
                _failure = _nameTable.Add("failure");
                _notAuthorized = _nameTable.Add("not-authorized");
            }
            if (_settings == null)
            {
                _settings = new XmlReaderSettings() { ConformanceLevel = ConformanceLevel.Fragment, IgnoreProcessingInstructions = true, IgnoreComments = true };
            }
            if (_namespaceManager == null)
            {
                _namespaceManager = new XmlNamespaceManager(_nameTable);
                _namespaceManager.AddNamespace("stream", "http://etherx.jabber.org/streams");
            }
            if (_parserContext == null)
            {
                _parserContext = new XmlParserContext(_nameTable, _namespaceManager, null, XmlSpace.Default);
            }
        }

        public void Parse(string response)
        {
            try
            {
                using (XmlReader reader = XmlReader.Create(new StringReader(response), _settings, _parserContext))
                {
                    while (reader.Read())
                    {
                        if (reader.NodeType == XmlNodeType.Element)
                        {
                            object localName = reader.LocalName;
                            if (_presence == localName)
                            {
                                this._listener.RaisePresenceChanged(reader.ReadOuterXml());
                            }
                            else if (_iq == localName)
                            {
                                ReadIq(reader);
                            }
                            else if (_message == localName)
                            {
                                this._listener.RaiseMessageReceived(reader.ReadOuterXml());
                            }
                            else if (_failure == localName && reader.NamespaceURI == NamespaceConstants.SaslNamespace)
                            {
                                ReadFailure(reader);
                                break;
                            }
                        }
                    }
                }
            }
            catch (XmlException ex)
            {
                Logger.Log("Response:" + response, "Parsing exception");
                Logger.Log(ex.Message);
            }
        }

        private void ReadFailure(XmlReader reader)
        {
            while (reader.Read())
            {
                object localName = reader.LocalName;
                if (reader.NodeType ==  XmlNodeType.Element && localName == _notAuthorized)
                {
                    Exception exception = new UnauthorizedAccessException("Authentication failure");
                    _listener.RaiseJidAvaible(exception);
                    throw exception;
                }
                else
                {
                    Debug.Fail("Unknown failure");
                }
            }
        }

        private void ReadIq(XmlReader reader)
        {
            string from = reader.GetAttribute("from");
            while (reader.Read())
            {
                switch(reader.NodeType)
                {
                    case XmlNodeType.Element:
                        object localName = reader.LocalName;
                        if (localName == _bind)
                        {
                            if (reader.ReadToDescendant("jid", NamespaceConstants.BindNamespace))
                            {
                                _listener.RaiseJidAvaible(new Jid(reader.ReadString()));
                            }
                        }
                        else if (localName == _query && reader.NamespaceURI == NamespaceConstants.RosterNamespace)
                        {
                            _listener.RaiseRosterLoaded(reader.ReadOuterXml());
                        }
                        else if (localName == _vcard && reader.NamespaceURI == NamespaceConstants.VcardNamespace)
                        {
                            _listener.RaiseVcardAvailable(reader.ReadOuterXml(), from);
                        }
                        break;
                    case XmlNodeType.EndElement:
                        if (reader.LocalName == _iq)
                        {
                            return;
                        }
                        break;
                }

            }
        }

    }
}
